package auth

import (
	"errors"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"mxshop-api/shared/auth/token"
)

const (
	AuthorizationHeader = "authorization"
	UserID              = "userID"
	Claims              = "claims"
	bearerPrefix        = "Bearer"
)

type tokenVerifier interface {
	Verify(token string) (token.Custom, error)
}

type tokenVerify struct {
	verifier tokenVerifier
}

func Middleware(signingKey string) gin.HandlerFunc {
	m := &tokenVerify{
		verifier: &token.JWTTokenVerifier{
			SigningKey: []byte(signingKey),
		},
	}
	return m.HandleReq()
}

func (t *tokenVerify) HandleReq() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.Request.Header.Get(AuthorizationHeader)
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"msg": "请求头中auth为空",
			})
			c.Abort()
			return
		}

		// 按空格分割
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == bearerPrefix) {
			c.JSON(http.StatusUnauthorized, gin.H{
				"msg": "请求头中auth格式有误",
			})
			c.Abort()
			return
		}

		// Verify 解析token包含的信息
		claims, err := t.verifier.Verify(parts[1])
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"msg": err.Error()})
			c.Abort()
			return
		}
		c.Set(Claims, claims)
		c.Set(UserID, claims.ID)
		c.Next()
	}
}

// ClaimsFromContext gets claims from gin.context.
// Returns unauthorized http status code response if no claims is available.
func ClaimsFromContext(ctx *gin.Context) (token.Custom, error) {
	v, ok := ctx.Get(Claims)
	if !ok {
		ctx.Status(http.StatusUnauthorized)
		return token.Custom{}, errors.New("unauthorized")
	}
	claims, ok := v.(token.Custom)
	if !ok {
		ctx.Status(http.StatusInternalServerError)
		return token.Custom{}, errors.New("claims assertion error")
	}
	return claims, nil
}

// UserIDFromContext gets user id from gin.context.
// Returns unauthorized http status code response if no user id is available.
func UserIDFromContext(ctx *gin.Context) (uint, error) {
	v, ok := ctx.Get(UserID)
	if !ok {
		ctx.Status(http.StatusUnauthorized)
		return 0, errors.New("unauthorized")
	}
	userID, ok := v.(uint)
	if !ok {
		ctx.Status(http.StatusInternalServerError)
		return 0, errors.New("user id assertion error")
	}
	return userID, nil
}
